package org.cainiao.oauth2.client.core.component;

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authorization.AuthorityAuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;

/**
 * <br />
 * <p>
 * Author: Cai Niao(wdhlzd@163.com)<br />
 */
@RequiredArgsConstructor
public class ScopeAuthorityAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {

    public static final String SCOPE_PREFIX = "SCOPE_";
    public static final String DYNAMIC_SCOPES_NAME = "cn_dynamic_scopes";
    private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

    private final Collection<String> scopes;
    private final Collection<String> authorities;
    private final HttpServletRequest request;

    @Override
    public void verify(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
        AuthorizationDecision decision = check(authentication, object);
        if (decision != null && !decision.isGranted()) {
            throw new AccessDeniedException("Access Denied");
        }
    }

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authenticationSupplier, RequestAuthorizationContext object) {
        Authentication authentication = authenticationSupplier.get();
        if (authentication == null) {
            setDynamicScopes();
            throw new AuthenticationCredentialsNotFoundException("no Authentication");
        }
        if (trustResolver.isAnonymous(authentication) || !authentication.isAuthenticated()) {
            setDynamicScopes();
            throw new CredentialsExpiredException("anonymous");
        }

        Set<String> grantedAuthorities = new HashSet<>();
        for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
            grantedAuthorities.add(grantedAuthority.getAuthority());
        }

        // 只要有一个 scope 没有，则需要以对应的方式登录
        Set<String> dynamicScopes = new HashSet<>();
        for (String scope : scopes) {
            if (!grantedAuthorities.contains(scope)) {
                dynamicScopes.add(removePrefix(scope, SCOPE_PREFIX));
            }
        }
        if (!dynamicScopes.isEmpty()) {
            request.getSession().setAttribute(DYNAMIC_SCOPES_NAME, dynamicScopes.toArray());
            throw new CredentialsExpiredException("lack of scope");
        }

        return new AuthorityAuthorizationDecision(authorities.isEmpty() || isGranted(authentication, authorities),
            AuthorityUtils.createAuthorityList(authorities));
    }

    private void setDynamicScopes() {
        Set<String> dynamicScopes = new HashSet<>();
        for (String scope : scopes) {
            dynamicScopes.add(removePrefix(scope, SCOPE_PREFIX));
        }
        if (!dynamicScopes.isEmpty()) {
            request.getSession().setAttribute(DYNAMIC_SCOPES_NAME, dynamicScopes.toArray());
        }
    }

    private boolean isGranted(Authentication authentication, Collection<String> authorities) {
        return authentication != null && isAuthorized(authentication, authorities);
    }

    private boolean isAuthorized(Authentication authentication, Collection<String> authorities) {
        for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
            if (authorities.contains(grantedAuthority.getAuthority())) {
                return true;
            }
        }
        return false;
    }

    public static String removePrefix(String str, String prefix) {
        if (str.indexOf(prefix) == 0) {
            return str.substring(prefix.length());
        }
        return str;
    }
}
